﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

using System.IO;

namespace KATS_Server
{
    /// <summary>
    /// An instance of the file server
    /// </summary>
    public class Server : IServer, IDisposable
    {
        /// <summary>
        /// The active file's info
        /// </summary>
        private FileInfo file;

        /// <summary>
        /// The active directory
        /// </summary>
        private DirectoryInfo directory;

        /// <summary>
        /// A binary writer
        /// </summary>
        private BinaryWriter fileWriter;

        /// <summary>
        /// A binary reader
        /// </summary>
        private BinaryReader fileReader;

        /// <summary>
        /// The number of active server threads
        /// </summary>
        private static int numberOfClients = 0;

        /// <summary>
        /// The number of active server threads
        /// </summary>
        public static int NumberOfClients
        {
            get { return numberOfClients; }
            set
            {
                numberOfClients = value;
                if (NumberOfClientsChanged != null)
                    NumberOfClientsChanged(new object(), new EventArgs());
            }
        }

        /// <summary>
        /// The number of server threads
        /// </summary>
        public static event EventHandler<EventArgs> NumberOfClientsChanged;

        /// <summary>
        /// Create a new server thread
        /// </summary>
        public Server()
        {
            NumberOfClients++;
        }

        /// <summary>
        /// Disconnect the client
        /// </summary>
        public void Disconnect()
        {

        }

        /// <summary>
        /// Displose of this object
        /// </summary>
        public void Dispose()
        {
            CloseFile();
            NumberOfClients--;
            if (serverToClientConnection != null)
            {
                // connection to client exists
                // terminate this connection
                if (serverToClientConnection.State != System.ServiceModel.CommunicationState.Closed && serverToClientConnection.State != System.ServiceModel.CommunicationState.Closing)
                    serverToClientConnection.Close();
                serverToClientConnection = null;
            }
        }

        /// <summary>
        /// Close any open connections to files
        /// </summary>
        public void CloseFile()
        {
            CloseReader();
            CloseWriter();
        }

        /// <summary>
        /// Close the file reader
        /// </summary>
        public void CloseReader()
        {
            if (fileReader != null)
            {
                fileReader.Close();
                fileReader = null;
            }
        }
        /// <summary>
        /// Close the file writer
        /// </summary>
        public void CloseWriter()
        {
            if (fileWriter != null)
            {
                fileWriter.Close();
                fileWriter = null;
            }
        }

        /// <summary>
        /// Set the active location
        /// </summary>
        /// <param name="location">The location of this file or folder</param>
        /// <returns>If the file was set correctly</returns>
        public bool SetLocation(string location)
        {
            try
            {
                location = Encryption.Decrypt(location);

                CloseFile();

                file = null;
                directory = null;

                // set the file
                DirectoryInfo di = new DirectoryInfo(location);
                if (!di.Exists)
                {
                    // a directory like this does not exist
                    // so is a file
                    file = new FileInfo(location);
                }
                else
                {
                    // is a directory
                    directory = di;
                }
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error [SetLocation()]: {0}", e.Message);
            }
            return false;
        }

        /// <summary>
        /// Get the file size, in bytes
        /// </summary>
        /// <param name="location">The file in question</param>
        /// <returns>The file size</returns>
        public long FileLength(string location)
        {
            try
            {
                location = Encryption.Decrypt(location);

                FileInfo fi = new FileInfo(location);
                return fi.Length;
            }
            catch (ArgumentException)
            {
                return -1;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error [FileLength()]. " + e.Message);
                Console.WriteLine(e.StackTrace);
                return -1;
            }
        }

        /// <summary>
        /// Get the length of a folder
        /// </summary>
        /// <param name="location">The folder location</param>
        /// <param name="recursive">Include subfolders?</param>
        /// <returns>The size of the folder in bytes</returns>
        public long DirectoryLength(string location, bool recursive)
        {
            location = Encryption.Decrypt(location);

            DirectoryInfo di = new DirectoryInfo(location);
            if (di.Exists)
                return getFolderLength(recursive, di);
            else
                return -1;
        }

        /// <summary>
        /// Get the length of a folder
        /// </summary>
        /// <param name="SubFolders">Include subfolders?</param>
        /// <param name="folder">The folder</param>
        /// <returns>The size in bytes</returns>
        private long getFolderLength(bool SubFolders, DirectoryInfo folder)
        {
            long size = 0;
            if (SubFolders)
            {
                DirectoryInfo[] dirs = folder.GetDirectories();
                foreach (DirectoryInfo di in dirs)
                {
                    size += getFolderLength(SubFolders, di);
                }
            }

            FileInfo[] files = folder.GetFiles();
            foreach (FileInfo fi in files)
            {
                size += fi.Length;
            }
            return size;
        }

        /// <summary>
        /// Send a file chunk to the server
        /// </summary>
        /// <param name="fileBytes">The file bytes that make up the file</param>
        /// <returns>If this chunk was successfully saved to the HDD</returns>
        public bool SendFileChunk(byte[] fileBytes)
        {
            try
            {
                // save the file onto the local computer
                fileWriter.Write(fileBytes);
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error [SendFileChunk()]: {0}", e.Message);
            }
            return false;
        }

        /// <summary>
        /// Get a chunk of the active file
        /// </summary>
        /// <returns></returns>
        public byte[] GetFileChunk()
        {
            byte[] fileChunk = fileReader.ReadBytes(Properties.Settings.Default.ChunkSize);
            return fileChunk;
        }

        /// <summary>
        /// Undo a read operation
        /// </summary>
        /// <returns>If the operation was successfull</returns>
        public bool UndoRead()
        {
            try
            {
                fileReader.BaseStream.Position -= Properties.Settings.Default.ChunkSize;
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error [UndoRead()]: {0}", e.Message);
                return false;
            }
        }

        /// <summary>
        /// Reset the file writer to the start of the file
        /// </summary>
        public void ResetWriter()
        {
            fileWriter.BaseStream.Position = 0;
        }

        /// <summary>
        /// Open the file writer on the active file
        /// </summary>
        public void OpenWriter()
        {
            fileWriter = new BinaryWriter(file.Open(FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite));
            fileWriter.BaseStream.Position = fileWriter.BaseStream.Length;
        }
        /// <summary>
        /// Open the file reader on the active file
        /// </summary>
        public void OpenReader()
        {
            fileReader = new BinaryReader(file.Open(FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite));
        }

        /// <summary>
        /// Create a directory on the server
        /// </summary>
        /// <param name="location">The location of the new directory</param>
        /// <returns>If this operation was successfull?</returns>
        public bool CreateDirectory(string location)
        {
            location = Encryption.Decrypt(location);
            try
            {
                DirectoryInfo di = new DirectoryInfo(location);
                if (!di.Exists)
                {
                    // folder does not exist, so create it
                    di.Create();
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Write a message out on the server screen
        /// </summary>
        /// <param name="msg">The message</param>
        public void PrintMessage(string msg)
        {
            msg = Encryption.Decrypt(msg);
            Console.WriteLine(msg);
        }

        /// <summary>
        /// Delete the active object on the server
        /// </summary>
        /// <returns>If the file was deleted</returns>
        public bool Delete()
        {
            try
            {
                this.CloseFile();
                if (file != null)
                {
                    if (file.Exists)
                        file.Delete();
                }
                if (directory != null)
                {
                    // then is a directory
                    if (directory.Exists)
                        directory.Delete(true);
                }
                return true;
            }
            catch (FileNotFoundException)
            {
                return true;
            }
            catch (DirectoryNotFoundException)
            {
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error [Delete()]: '{0}'", e.ToString());
            }
            return false;
        }

        /// <summary>
        /// Move a active object to the target location
        /// </summary>
        /// <param name="target">The target location for the file</param>
        /// <returns>If move was successfull</returns>
        public bool Move(string target)
        {
            try
            {
                target = Encryption.Decrypt(target);

                if (this.file != null)
                {
                    FileInfo file_C = new FileInfo(this.file.FullName);
                    // only try to do the action if file exists
                    // must be a single file
                    if (file_C.Exists)
                    {
                        moveAFile(new FileInfo(file_C.FullName), new FileInfo(target));
                    }
                }

                if (this.directory != null)
                {
                    // then is folder
                    if (directory.Exists)
                        moveAFolder(directory, new DirectoryInfo(target));
                }
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error [Move()]: {0}", e.Message);
            }
            return false;
        }

        /// <summary>
        /// Move a folder
        /// </summary>
        /// <param name="from">Source directory</param>
        /// <param name="to">Target Directory</param>
        private void moveAFolder(DirectoryInfo from, DirectoryInfo to)
        {
            // move the directory and any subfolders

            if (from.Root.ToString() != to.Root.ToString())
            {
                // if the directories are not in the same harddrive
                // copy the folder then delete the original folder
                copyAFolder(from, to);
                from.Delete(true);
            }
            else
            {
                // just use the move command as in the same harddrive
                from.MoveTo(to.FullName);
            }
        }

        /// <summary>
        /// Move a file
        /// </summary>
        /// <param name="from">Source file</param>
        /// <param name="to">Target file</param>
        private void moveAFile(FileInfo from, FileInfo to)
        {
            if (!to.Directory.Exists)
            {
                // if target folder does not exists, create it
                to.Directory.Create();
            }
            if (to.Exists)
            {
                // if the target file already exists, delete it
                to.Delete();
            }

            // move the file
            if (from.Directory.Root.ToString() != to.Directory.Root.ToString())
            {
                // if the directories are not in the same harddrive
                // copy the folder then delete the original folder
                copyAFile(from, to);
                from.Delete();
            }
            else
            {
                // just use the move command as on the same harddrive
                from.MoveTo(to.FullName);
            }
        }

        /// <summary>
        /// Copy a file or folder
        /// </summary>
        /// <param name="target">Target Location</param>
        /// <returns>If the copy was successful</returns>
        public bool Copy(string target)
        {
            target = Encryption.Decrypt(target);

            try
            {
                // source is a file
                if (file != null)
                {
                    copyAFile(file, new FileInfo(target));
                }
                // then source is a directory
                if (directory != null)
                {
                    copyAFolder(directory, new DirectoryInfo(target));
                }
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Error [Copy()]: {0}", e.Message);
            }
            return false;
        }

        /// <summary>
        /// Copy a directory
        /// </summary>
        /// <param name="from">Source directory</param>
        /// <param name="to">Target Directory</param>
        private void copyAFolder(DirectoryInfo from, DirectoryInfo to)
        {
            // copy the directory and any subfolders

            // get subdirectorys to copy
            if (!to.Exists)
            {
                // create the target subdirectory if required
                to.Create();
            }
            DirectoryInfo[] subFolders = from.GetDirectories();
            foreach (DirectoryInfo subFolder in subFolders)
            {
                // copy the subfolders
                DirectoryInfo subFrom = subFolder;
                DirectoryInfo subTo = new DirectoryInfo(string.Format("{0}\\{1}", to.FullName, subFrom.Name));
                //Console.WriteLine("Copying Folder\n->From: {0}\n->To: {1}", subFrom.FullName, subTo.FullName);
                copyAFolder(subFrom, subTo);
            }
            // get array of files to copy
            FileInfo[] from_files = from.GetFiles();
            foreach (FileInfo from_file in from_files)
            {
                // copy each file
                FileInfo to_file = new FileInfo(string.Format("{0}\\{1}", to.FullName, from_file.Name));
                //Console.WriteLine("Copying File\n->From: {0}\n->To: {1}", from_file.FullName, to_file.FullName);
                copyAFile(from_file, to_file);
            }
        }

        /// <summary>
        /// Copy the files across
        /// </summary>
        /// <param name="from">Source file</param>
        /// <param name="to">Target location</param>
        private void copyAFile(FileInfo from, FileInfo to)
        {
            if (!to.Directory.Exists)
            {
                // if target folder does not exists, create it
                to.Directory.Create();
            }
            if (to.Exists)
            {
                // if the target file already exists, delete it
                to.Delete();
            }
            from.CopyTo(to.FullName);
        }

        /// <summary>
        /// Does the active object exist?
        /// </summary>
        /// <returns>Exists?</returns>
        public bool Exists()
        {
            if (directory != null)
            {
                if (directory.Exists)
                    return true;
            }
            if (file != null)
            {
                if (file.Exists)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Does the filesystem object exist
        /// </summary>
        /// <param name="Loc">Location to check</param>
        /// <returns>True if valid</returns>
        public bool Exist(string Loc)
        {
            // set the file
            Loc = Encryption.Decrypt(Loc);
            DirectoryInfo di = new DirectoryInfo(Loc);
            if (!di.Exists)
            {
                // a directory like this does not exist
                // so is a file
                FileInfo file = new FileInfo(Loc);
                return file.Exists;
            }
            else
            {
                // is a directory
                return di.Exists;
            }
        }

        /// <summary>
        /// Get an array of the full locations of the files in the current folder
        /// </summary>
        /// <returns>Array of full file names</returns>
        public string[] GetDirectoryFiles()
        {
            return GetDirectoryFilesS(directory.FullName);
        }

        /// <summary>
        /// Get the files in the directory
        /// </summary>
        /// <param name="folder">The directory</param>
        /// <returns>Array of full file names</returns>
        public string[] GetDirectoryFilesS(string dir)
        {
            dir = Encryption.Decrypt(dir);
            try
            {
                DirectoryInfo directory = new DirectoryInfo(dir);
                FileInfo[] files = directory.GetFiles();

                List<string> stringFiles = new List<string>();
                foreach (FileInfo fi in files)
                {
                    if ((fi.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                    {
                        // file is hidden
                    }
                    else
                    {
                        string location = fi.FullName;
                        location = Encryption.Encrypt(location);
                        stringFiles.Add(location);
                    }
                }
                return stringFiles.ToArray();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return new string[0];
            }
        }

        public string[] GetDirectoryFilesAndSizes(string dir)
        {
            dir = Encryption.Decrypt(dir);
            try
            {
                DirectoryInfo directory = new DirectoryInfo(dir);
                FileInfo[] files = directory.GetFiles();

                List<string> stringFiles = new List<string>();
                foreach (FileInfo fi in files)
                {
                    if ((fi.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                    {
                        // file is hidden
                    }
                    else
                    {
                        string location = string.Format("{0}?{1}",fi.FullName,fi.Length);
                        location = Encryption.Encrypt(location);
                        stringFiles.Add(location);
                    }
                }
                return stringFiles.ToArray();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return new string[0];
            }
        }

        /// <summary>
        /// Get the sub folders of the current directory
        /// </summary>
        /// <returns></returns>
        public string[] GetSubDirectories()
        {
            return GetSubDirectoriesS(directory.FullName);
        }

        /// <summary>
        /// Get the sub directories for the folder provided
        /// </summary>
        /// <param name="Directory">The target directory</param>
        /// <returns>Array of the sub directories</returns>
        public string[] GetSubDirectoriesS(string Directory)
        {
            Directory = Encryption.Decrypt(Directory);
            try
            {
                DirectoryInfo directory = new DirectoryInfo(Directory);
                DirectoryInfo[] folders = directory.GetDirectories();

                List<string> stringFolders = new List<string>();
                foreach (DirectoryInfo di in folders)
                {
                    if ((di.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                    {
                        // folder is hidden
                    }
                    else
                    {
                        string location = di.FullName;
                        location = Encryption.Encrypt(location);
                        stringFolders.Add(location);
                    }
                }
                return stringFolders.ToArray();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return new string[0];
            }
        }

        /// <summary>
        /// Rename a target object
        /// </summary>
        /// <param name="target">The target</param>
        /// <param name="newTarget">The new target</param>
        /// <returns>If the action was performed</returns>
        public bool Rename(string target, string newTarget)
        {
            target = Encryption.Decrypt(target);
            newTarget = Encryption.Decrypt(newTarget);

            try
            {
                FileInfo fi = new FileInfo(target);
                DirectoryInfo di = new DirectoryInfo(target);
                if (di.Exists == true)
                {
                    // target is a directory
                    di.MoveTo(newTarget);
                }
                else
                {
                    // target is a file
                    fi.MoveTo(newTarget);
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Get the HDDs on the server
        /// </summary>
        /// <returns></returns>
        public string[] GetHDDs()
        {
            DriveInfo[] driveInfo = DriveInfo.GetDrives();
            List<string> HDDs = new List<string>();
            foreach (DriveInfo drive in driveInfo)
            {
                if (drive.DriveType == DriveType.Fixed)
                {
                    string Drive = drive.Name;
                    Drive = Encryption.Encrypt(Drive);
                    HDDs.Add(Drive);
                }
            }
            return HDDs.ToArray();
        }

        /// <summary>
        /// The server to client communication path
        /// </summary>
        public ServerToClientConnection.ServerClient serverToClientConnection;


        /// <summary>
        /// Authenticate the connection to the server. If successful, make loop back connection to client
        /// </summary>
        /// <param name="username">Username</param>
        /// <param name="password">Password</param>
        /// <returns>Is valid?</returns>
        public bool Authenticate(string username, string password)
        {
            try
            {
                username = Encryption.Decrypt(username);
                password = Encryption.Decrypt(password);

                foreach (User user in Users)
                {
                    if (user.username == username && user.password == password)
                    {
                        // username and password match up with an account
                        serverToClientConnection = new ServerToClientConnection.ServerClient();
                        return true;
                    }
                }

                Console.WriteLine("Authentication Failed: Username: {0}. Password: {1}", username, password);

                // then password is invalid
                
                // disconnect the session
                System.ServiceModel.OperationContext.Current.Channel.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }

            return false;
        }

        /// <summary>
        /// The users for this server
        /// </summary>
        private static User[] _users;

        /// <summary>
        /// The users for the server
        /// </summary>
        private static User[] Users
        {
            get
            {
                if (_users == null)
                {
                    _users = User.LoadUsers();
                }
                return _users;
            }
        }

        
        /// <summary>
        /// Make this connection wait
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public bool Wait(int time)
        {
            if (time == 0)
                return true;
            System.Threading.Thread.Sleep(time);
            return true;
        }



    }
}
